{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# 利用用户标签数据\n",
    "推荐系统的目的是联系用户的兴趣和物品，这种联系需要依赖不同的媒介。\n",
    "\n",
    "目前流行的推荐系统基本上通过3种方式联系用户兴趣和物品：\n",
    "\n",
    "1.  利用用户喜欢过的物品，给用户推荐与他喜欢过的物品相似的物品（ItemCF）\n",
    "2.  利用和用户兴趣相似的其他用户，给用户推荐那些和他们兴趣爱好相似的其他用户喜欢的物品（UserCF）\n",
    "3.  **通过一些特征（feature）联系用户和物品，给用户推荐那些具有用户喜欢的特征的物品**\n",
    "\n",
    "![img](../recsys_images/user_item_relations.png \"推荐系统联系用户和物品的几种途径\")\n",
    "\n",
    "**特征** 有不同的表现方式，可表现为物品的属性集合，也可表现为隐语义向量（latent factor vector），另外一种重要的特征表现方式就是 **标签** 。\n",
    "\n",
    "**标签** 是一种无层次化结构的、用来描述信息的关键词，它可以用来描述物品的语义。<sup><a id=\"fnr.1\" class=\"footref\" href=\"#fn.1\">1</a></sup>\n",
    "\n",
    "根据给物品打标签的人的不同，标签应用一般分为两种：\n",
    "\n",
    "1.  让作者或者专家给物品打标签；\n",
    "2.  让普通用户给物品打标签，也就是UGC（User Generated Content，用户生成的内容）的标签应用。\n",
    "\n",
    "**UGC的标签系统是一种表示用户兴趣和物品语义的重要方式。** 当一个用户对一个物品打上一个标签，这个标签一方面描述了用户的兴趣，另一方面则表示了物品的语义，从而将用户和物品联系了起来。\n",
    "\n",
    "本章主要讨论UGC的标签应用，研究用户给物品打标签的行为，探讨如何通过分析这种行为给用户进行个性化推荐。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orgc10c7e6\"></a>\n",
    "## UGC标签系统的代表应用\n",
    "\n",
    "-   Delicious\n",
    "\n",
    "-   CiteULike\n",
    "\n",
    "-   Last.fm\n",
    "\n",
    "-   豆瓣\n",
    "\n",
    "标签系统在各种各样的（音乐、视频和社交等）网站中都得到了广泛应用。\n",
    "\n",
    "标签系统的最大优势在于可以发挥群体的智能，获得对物品内容信息比较准确的关键词描述，而准确的内容信息是提升个性化推荐系统性能的重要资源。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org5826b36\"></a>\n",
    "### 标签系统的作用\n",
    "\n",
    "GroupLens的Shilads Wieland Sen在MoveLens电影推荐系统上的研究，得到的标签系统的不同作用以及每种作用能够影响多大的人群，如下所示：\n",
    "\n",
    "-   **表达:** 标签系统帮助我表达对物品的看法。（30%的用户同意）\n",
    "-   **组织:** 打标签帮助我组织我喜欢的电影。（23%的用户同意）\n",
    "-   **学习:** 打标签帮助我增加对电影的了解。（27%的用户同意）\n",
    "-   **发现:** 标签系统使我更容易发现喜欢的电影。（19%的用户同意）\n",
    "-   **决策:** 标签系统帮助我判定是否看某一部电影。（14%的用户同意）\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orga1f15c1\"></a>\n",
    "## 标签系统中的推荐问题\n",
    "\n",
    "标签系统中的推荐问题主要有以下两个：\n",
    "\n",
    "1.  如何利用用户打标签的行为为其推荐物品（基于标签的推荐）？\n",
    "2.  如何在用户给物品打标签时为其推荐适合该物品的标签（标签推荐）？\n",
    "\n",
    "首先需要解答的3个问题：\n",
    "\n",
    "1.  用户为什么要打标签？\n",
    "2.  用户怎么打标签？\n",
    "3.  用户打什么样的标签？\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org0151ea9\"></a>\n",
    "### 用户为什么进行标注<sup><a id=\"fnr.2\" class=\"footref\" href=\"#fn.2\">2</a></sup>\n",
    "\n",
    "-   **社会维度**\n",
    "\n",
    "    有些用户标注是给内容上传者使用的（便于上传者组织自己的信息），而有些用户标注是给广大用户使用的（便于帮助其他用户找到信息）。\n",
    "\n",
    "-   **功能维度**\n",
    "\n",
    "    有些标注用于更好地组织内容，方便用户将来的查找，而另一些标注用于传达某种信息，比如照片的拍摄时间和地点等。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org616a630\"></a>\n",
    "### 用户如何打标签\n",
    "\n",
    "通过对Delicious数据集<sup><a id=\"fnr.3\" class=\"footref\" href=\"#fn.3\">3</a></sup>的研究，标签的流行度分布也呈现长尾分布。\n",
    "\n",
    "\\begin{equation}\n",
    "\\begin{split}\n",
    "& \\log{n(k)} = \\alpha \\log{k} + \\beta = \\log{k^\\alpha} \\cdot e^\\beta \\\\\n",
    "& n(k) = e^\\beta \\cdot k^\\alpha = \\gamma \\cdot k^\\alpha \\\\\n",
    "\\end{split} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "上面公式中， $k$ 表示流行度， $n(k)$ 表示流行度为 $k$ 的标签总数。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org1735e58\"></a>\n",
    "### 用户打什么样的标签\n",
    "\n",
    "用户往往不能够给物品打上准确描述物品内容属性的关键词，而可能会给物品打上各种各样奇怪的标签。\n",
    "\n",
    "下面是统计的Delicious数据集上的标签分类：\n",
    "\n",
    "-   **表明物品是什么**\n",
    "-   **表明物品的种类**\n",
    "-   **表明谁拥有物品**\n",
    "-   **表达用户的观点**\n",
    "\n",
    "    比如 funny（有趣）、boring（无聊）等\n",
    "\n",
    "-   **用户相关的标签**\n",
    "\n",
    "    比如 my favorite（我最喜欢的）、my comment（我的评论）等\n",
    "\n",
    "-   **用户的任务**\n",
    "\n",
    "    比如 to read（即将阅读）、job search（找工作）等\n",
    "\n",
    "**不同的网站一般会用不同的标签分类系统。** 下面是 Hulu 对电视剧的标签分类：\n",
    "\n",
    "-   **类型（Genre）**\n",
    "\n",
    "    表示类别。\n",
    "\n",
    "-   **时间（Time）**\n",
    "\n",
    "    发布时间、事件发生的时间等。\n",
    "\n",
    "-   **人物（People）**\n",
    "-   **地点（Place）**\n",
    "-   **语言（Language）**\n",
    "-   **奖项（Awards）**\n",
    "-   其他（Details）\n",
    "\n",
    "    不能归类到上面各类中的其他所有标签。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orga3d35fd\"></a>\n",
    "## 基于标签的推荐系统\n",
    "\n",
    "用户用标签来描述对物品的看法，因此标签是联系用户和物品的纽带，也是反应用户兴趣的重要数据源。\n",
    "\n",
    "一个用户标签行为的数据集一般由一个三元组的集合表示，其中记录 $(u, i, b)$ 表示用户 $u$ 给物品 $i$ 打上了标签 $b$ 。<sup><a id=\"fnr.4\" class=\"footref\" href=\"#fn.4\">4</a></sup>\n",
    "\n",
    "本章采用的两个不同的数据集，一个是 **Delicious数据集** （每一行由时间、用户ID、网页URL、标签4部分构成），另一个是 **CiteULike数据集** （每行由物品ID、用户ID、时间、标签4部分构成）。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org2b3e5ec\"></a>\n",
    "### 实验设置\n",
    "\n",
    "将数据集随机分成10份。分割的键值是用户和物品，不包括标签。挑选1份作为测试集，剩下的9份作为训练集，通过学习训练集中的用户标签数据预测测试集上用户会给什么物品打标签。\n",
    "\n",
    "对于用户 $u$ ，令 $R(u)$ 为给用户 $u$ 的长度为 $N$ 的推荐列表，里面包含我们认为用户会打标签的物品。令 $T(u)$ 是测试集中用户 $u$ 实际上打过标签的物品集合。\n",
    "\n",
    "利用准确率和召回率评测个性化推荐算法的精度。\n",
    "\n",
    "\\begin{equation}\n",
    "\\begin{split}\n",
    "& Precision = \\frac{|R(u) \\cap T(u)|}{|R(u)|} \\\\\n",
    "& Recall = \\frac{|R(u) \\cap T(u)|}{|T(u)|} \\\\\n",
    "\\end{split} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "将上面的实验进行10次，每次选择不同的测试集，然后将每次实验的准确率和召回率的平均值作为最终的评测结果。\n",
    "\n",
    "下面是其他的评测指标：\n",
    "\n",
    "覆盖率：\n",
    "\n",
    "\\begin{equation}\n",
    "Coverage = \\frac{|\\underset{u \\in U}{U} R(u)|}{|I|} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "本章用物品标签向量的余弦相似度度量物品之间的相似度。对于每个物品 $i$ ，<code>item_tags[i]</code>存储了物品 $i$ 的标签向量，其中 $item_tags[i][b]$ 是对物品 $i$ 打标签 $b$ 的次数，则物品 $i$ 和 $j$ 的余弦相似度可通过如下代码计算：\n",
    "\n",
    "```python\n",
    "def cosinesim(item_tags, i, j):\n",
    "    ret = 0\n",
    "    for b, wib in item_tags[i].items():\n",
    "        if b in item_tags[j].items():\n",
    "            ret += wib * item_tags[j][b]\n",
    "    ni = 0\n",
    "    nj = 0\n",
    "    for b, w in item_tags[i].items():\n",
    "        ni += w * w\n",
    "    for b, w in item_tags[j].items():\n",
    "        nj += w * w\n",
    "    if ret == 0:\n",
    "        return 0\n",
    "    return ret / math.sqrt(ni * nj)\n",
    "```\n",
    "\n",
    "得到物品之间的相似度度量后，通过如下公式计算一个推荐列表的多样性：\n",
    "\n",
    "\\begin{equation}\n",
    "Diversity = 1 - \\frac{\\underset{i \\in R(u)}{\\sum} \\underset{j \\in R(u), j \\neq i}{\\sum} Sim(item\\_tags[i],item\\_tags[j])}{\n",
    "\\begin{pmatrix}\n",
    "  |R(u)|\\\\\n",
    "  2\\\\\n",
    "\\end{pmatrix}} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "伪代码如下：\n",
    "\n",
    "```python\n",
    "def Diversity(item_tags, recommend_items):\n",
    "    ret = 0\n",
    "    n=0\n",
    "    for i in recommend_items.keys():\n",
    "        for j in recommend_items.keys():\n",
    "             if i == j:\n",
    "                 continue\n",
    "             ret += CosineSim(item_tags, i, j)\n",
    "             n += 1\n",
    "    return ret / (n * 1.0)\n",
    "```\n",
    "\n",
    "推荐系统的多样性为所有用户推荐列表多样性的平均值。\n",
    "\n",
    "推荐结果的新颖性直接用推荐结果的平均热门程度（AveragePopularity）度量。对于物品 $i$ ，定义它的流行度<code>item_pop(i)</code>为给这个物品打过标签的用户数。而对推荐系统，定义它的热门度如下：\n",
    "\n",
    "\\begin{equation}\n",
    "AveragePopularity = \\frac{\\underset{u}{\\sum} \\underset{i \\in R(u)}{\\sum} \\log{(1 + item\\_pop(i))}}{\\underset{u}{\\sum} \\underset{i \\in R(u)}{\\sum} 1} \\nonumber\n",
    "\\end{equation}\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org9418d97\"></a>\n",
    "### 一个最简单算法\n",
    "\n",
    "一个最简单的基于用户标签的推荐算法：\n",
    "\n",
    "1.  统计每个用户最常用的标签\n",
    "2.  对于每个标签，统计被打过这个标签次数最多的物品\n",
    "3.  对于一个用户，首先找到其最常用的标签，然后找到具有这些标签的最热门物品推荐给这个用户\n",
    "\n",
    "对于上面的算法，用户 $u$ 对物品 $i$ 的兴趣公式如下：\n",
    "\n",
    "\\begin{equation}\n",
    "p(u,i) = \\underset{b}{\\sum}n_{u,b}n_{b,i} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "$B(u)$ 是用户 $u$ 打过的标签集合， $B(i)$ 是物品 $i$ 被打过的标签集合， $n_{u,b}$ 是用户 $u$ 打过标签 $b$ 的次数， $n_{b,i}$ 是物品 $i$ 被打过标签 $b$ 的次数。用 **SimpleTagBased** 标记这个算法。\n",
    "\n",
    "在Python中，遵循如下约定:\n",
    "\n",
    "-   用<code>records</code>存储标签数据的三元组，其中<code>records[i] = [user, item, tag]</code>\n",
    "-   用<code>user_tags</code>存储 $n_{u,b}$ ，其中<code>user_tags[u][b] =</code>$n_{u,b}$\n",
    "-   用<code>tag_items</code>存储 $n_{b,i}$ ，其中<code>tag_items[b][i] =</code>$n_{b,i}$\n",
    "\n",
    "从<code>records</code>中统计出<code>user_tags</code>和<code>tag_items</code>：\n",
    "\n",
    "```python\n",
    "def init_stat(records):\n",
    "    user_tags = dict()\n",
    "    tag_items = dict()\n",
    "    user_items = dict()\n",
    "    for user, item, tag in records.items():\n",
    "         addValueToMat(user_tags, user, tag, 1)\n",
    "         addValueToMat(tag_items, tag, item, 1)\n",
    "         addValueToMat(user_items, user, item, 1)\n",
    "```\n",
    "\n",
    "统计出<code>user_tags</code>和<code>tag_items</code>之后，通过如下程序对用户进行个性化推荐：\n",
    "\n",
    "```python\n",
    "def recommend(user):\n",
    "    recommend_items = dict()\n",
    "    tagged_items = user_items[user]\n",
    "    for tag, wut in user_tags[user].items():\n",
    "         for item, wti in tag_items[tag].items():\n",
    "             # if items have been tagged, do not recommend them\n",
    "             if item in tagged_items:\n",
    "                 continue\n",
    "             if item not in recommend_items:\n",
    "                 recommend_items[item] = wut * wti\n",
    "             else:\n",
    "                 recommend_items[item] += wut * wti\n",
    "    return recommend_items\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orgaf14bfa\"></a>\n",
    "### 算法的改进\n",
    "<a id=\"org2db2e10\"></a>\n",
    "\n",
    "#### TF-IDF\n",
    "\n",
    "SimpleTagBased算法预测用户对物品感兴趣的公式倾向于给热门标签对应的热门物品很大的权重，因此会造成推荐热门的物品给用户，从而降低推荐结果的新颖性。另外，这个公式利用用户的标签向量对用户兴趣建模，其中每个标签都是用户使用过的标签，而标签的权重是用户使用该标签的次数。这种建模方法的缺点是给热门标签过大的权重，从而不能反应用户个性化的兴趣。\n",
    "\n",
    "可以借鉴TF-IDF的思想，对这一公式进行改进：\n",
    "\n",
    "\\begin{equation}\n",
    "p(u,i) = \\underset{b}{\\sum} \\frac{n_{u,b}}{\\log{(1+n_{b}^{(u)})}} n_{b,i} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "$n_{b}^{(u)}$ 记录了标签 $b$ 被多少个不同的用户使用过。这个算法记为 **TagBasedTFIDF** 。\n",
    "\n",
    "同理，借鉴TF-IDF的思想对热门物品进行惩罚，从而得到如下公式：\n",
    "\n",
    "\\begin{equation}\n",
    "p(u,i) = \\underset{b}{\\sum} \\frac{n_{u,b}}{\\log{(1+n_{b}^{(u)})}} \\frac{n_{b,i}}{\\log{(1+n_{i}^{(u)})}} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "其中， $n_{i}^{(u)}$ 记录了物品 $i$ 被多少个不同的用户打过标签。这个算法记为 **TagBasedTFIDF++** 。\n",
    "\n",
    "**适当惩罚热门标签和热门物品，在增进推荐结果个性化的同时并不会降低推荐结果的离线精度。**\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org6357913\"></a>\n",
    "#### 数据稀疏性\n",
    "\n",
    "在前面的算法中，用户兴趣和物品的联系是通过 $B(u) \\cap B(i)$ 中的标签建立的。为了提高推荐的准确率，可能要对标签集合做扩展，比如若用户曾经用过“推荐系统”这个标签，可以将这个标签的相似标签也加入到用户标签集合中，比如“个性化”、“协同过滤”等标签。\n",
    "\n",
    "进行标签扩展有很多方法，其中常用的有话题模型(topic model)，这里介绍一种基于邻域的方法。\n",
    "\n",
    "标签扩展的本质是对每个标签找到和它相似的标签，也就是计算标签之间的相似度。最简单的相似度可以是同义词。如果有一个同义词词典，就可以根据这个词典进行标签扩展。如果没有这个词典，可以从数据中统计出标签的相似度。\n",
    "\n",
    "如果认为同一个物品上的不同标签具有某种相似度，那么当两个标签同时出现在很多物品的标签集合中时，就可以认为这两个标签具有较大的相似度。对于标签 $b$ ，令 $N(b)$ 为有标签 $b$ 的物品的集合， $n_{b,i}$ 为给物品 $i$ 打上标签 $b$ 的用户数，可以通过如下余弦相似度公式计算标签 $b$ 和标签 $b'$ 的相似度：\n",
    "\n",
    "\\begin{equation}\n",
    "sim(b,b') = \\frac{\\underset{i \\in N(b) \\cap N(b')}{\\sum} n_{b,i} n_{b',i}}{\\sqrt{\\underset{i \\in N(b)}{\\sum} n_{b,i}^2 \\underset{i \\in N(b')}{\\sum} n_{b',i}^2}} \\nonumber\n",
    "\\end{equation}\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org8fbe8c8\"></a>\n",
    "#### 标签清理\n",
    "\n",
    "-   不是所有标签都能反应用户的兴趣。\n",
    "    -   表示情绪的标签\n",
    "    -   词形不同、词义相同的标签\n",
    "\n",
    "-   将标签作为推荐解释\n",
    "\n",
    "如果要把标签呈现给用户，将其作为给用户推荐某一个物品的解释，对标签的质量要求就很高。首先，这些标签不能包含没有意义的停止词或者表示情绪的词，其次这些推荐解释里不能包含很多意义相同的词语。\n",
    "\n",
    "一般来说有如下标签清理方法:\n",
    "\n",
    "-   去除词频很高的停止词；\n",
    "-   去除因词根不同造成的同义词，比如 recommender system和recommendation system；\n",
    "-   去除因分隔符造成的同义词，比如 collaborative\\_filtering 和 collaborative-filtering。\n",
    "\n",
    "为了控制标签的质量，很多网站也采用了让用户进行反馈的思想，即让用户告诉系统某个标签是否合适。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orgd1e016f\"></a>\n",
    "### 基于标签的推荐解释\n",
    "\n",
    "基于标签的推荐其最大好处是可以利用标签做推荐解释，这方面的代表性应用是豆瓣的个性化推荐系统。\n",
    "\n",
    "豆瓣读书的个性化推荐应用——“豆瓣猜”。\n",
    "\n",
    "-   **提高了推荐结果的多样性**\n",
    "\n",
    "    通过标签云，展示了用户的所有兴趣，然后让用户自己根据他今天的兴趣选择相关的标签，得到推荐结果，从而极大地提高了推荐结果的多样性，使得推荐结果更容易满足用户多样的兴趣。\n",
    "\n",
    "-   **提供了推荐解释功能**\n",
    "\n",
    "    用户通过标签云界面可以知道系统给自己推荐的每一件物品都是基于其认为自己对某个标签感兴趣。对于每个标签，用户总能通过回忆自己之前的行为知道自己是否真的对这个标签感兴趣。\n",
    "\n",
    "    首先让用户觉得标签云是有道理的，然后让用户觉得从某个标签推荐出的某件物品也是有道理的。\n",
    "\n",
    "GroupLens的研究人员Jesse Vig对基于标签的解释进行了深入研究。<sup><a id=\"fnr.5\" class=\"footref\" href=\"#fn.5\">5</a></sup>和 **SimpleTagBased** 算法类似，Jesse Vig将用户和物品之间的关系变成了用户对标签的兴趣（tag preference）和标签与物品的相关度（tag relevance），然后作者用同一种推荐算法给用户推荐物品，但设计了4种标签解释的展示界面。\n",
    "\n",
    "-   **RelSort:** 对推荐物品做解释时使用的是用户以前使用过且物品上有的标签，给出了用户对标签的兴趣和标签与物品的相关度，但标签按照和物品的相关度排序。\n",
    "-   **PrefSort:** 对推荐物品做解释时使用的是用户以前使用过且物品上有的标签，给出了用户对标签的兴趣和标签与物品的相关度，但标签按照用户的兴趣程度排序。\n",
    "-   **RelOnly:** 对推荐物品做解释时使用的是用户以前使用过且物品上有的标签，给出了标签与物品的相关度，且标签按照和物品的相关度排序。\n",
    "-   **PrefOnly:** 对推荐物品做解释时使用的是用户以前使用过且物品上有的标签，给出了用户对标签的兴趣程度，且标签按照用户的兴趣程度排序。\n",
    "\n",
    "作者对用户设计了3种调查问卷。\n",
    "\n",
    "首先是关于推荐解释的调查问卷，作者问了如下3个问题：\n",
    "\n",
    "1.  推荐解释帮助我理解这部电影为什么会被推荐给我：对于这个问题用户认为 **RelSort>PrefOnly>=PrefSort>RelOnly** 。\n",
    "2.  推荐解释帮助我判定是否喜欢推荐的电影：对于这个问题用户认为 **RelSort>PrefSort> PrefOnly>RelOnly** 。\n",
    "3.  推荐解释帮助我判定观看这部电影是否符合我现在的兴趣：对于这个问题用户认为 **RelSort>PrefSort>RelOnly>PrefOnly** 。\n",
    "\n",
    "然后，作者调查了用户对不同类型标签的看法。\n",
    "\n",
    "作者将标签分为主观类（比如对电影的看法）和客观类（比如对电影内容的描述）。作者对每种类型的标签同样问了上面3个问题。\n",
    "\n",
    "-   这个标签帮助我理解这部电影为什么会被推荐给我：用户认为客观类标签优于主观类标签。\n",
    "-   这个标签帮助我判定是否喜欢推荐的电影：用户认为客观类标签优于主观类标签。\n",
    "-   这个标签帮助我判定观看这部电影是否符合我现在的兴趣：用户认为客观类标签优于主观类标签。\n",
    "\n",
    "**客观事实类的标签优于主观感受类标签。**\n",
    "\n",
    "最后，作者询问了用户对4种不同推荐解释界面的总体满意度，结果显示 **PrefOnly>RelSort>PrefSort>RelOnly** 。\n",
    "\n",
    "结论：\n",
    "\n",
    "-   用户对标签的兴趣对帮助用户理解为什么给他推荐某个物品更有帮助；\n",
    "-   用户对标签的兴趣和物品标签相关度对于帮助用户判定自己是否喜欢被推荐物品具有同样的作用；\n",
    "-   物品标签相关度对于帮助用户判定被推荐物品是否符合他当前的兴趣更有帮助；\n",
    "-   客观事实类标签相比主观感受类标签对用户更有作用。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orgd1748b9\"></a>\n",
    "## 给用户推荐标签\n",
    "\n",
    "让用户能够给物品打上高质量的标签，这样才能促进标签系统的良性循环。\n",
    "\n",
    "很多标签系统都设计了标签推荐模块给用户推荐标签。\n",
    "\n",
    "\n",
    "<a id=\"orgacc8690\"></a>\n",
    "\n",
    "### 为什么要给用户推荐标签\n",
    "\n",
    "一般认为，给用户推荐标签有以下好处：\n",
    "\n",
    "-   **方便用户输入标签**\n",
    "\n",
    "-   **提高标签质量**\n",
    "\n",
    "    同一个语义不同的用户可能用不同的词语来表示。这些同义词会使标签的词表变得很庞大，而且会使计算相似度不太准确。而使用推荐标签时，可以对词表进行选择，首先保证词表不出现太多的同义词，同时保证出现的词都是一些比较热门的、有代表性的词。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org1eff916\"></a>\n",
    "### 如何给用户推荐标签\n",
    "\n",
    "4种简单的给用户 $u$ 推荐和物品 $i$ 相关标签的方法：\n",
    "\n",
    "1.  给用户 $u$ 推荐整个系统里最热门的标签（ **PopularTags** ）\n",
    "\n",
    "    令<code>Tags[b]</code>为标签 $b$ 的热门程度，则算法实现如下：\n",
    "\n",
    "    ```python\n",
    "    def recommend_popular_tags(user, item, tags, N):\n",
    "        return sorted(tags.items(), key=itemgetter(1), reverse=True)[:N]\n",
    "    ```\n",
    "\n",
    "2.  给用户 $u$ 推荐物品 $i$ 上最热门的标签（ **ItemPopularTags** ）\n",
    "\n",
    "    令<code>item_tags[i][b]</code>为物品 $i$ 被打上标签 $b$ 的次数，则算法实现如下：\n",
    "\n",
    "    ```python\n",
    "    def recommend_item_popular_tags(user, item, item_tags, N):\n",
    "        return sorted(item_tags[item].items(), key=itemgetter(1), reverse=True)[:N]\n",
    "    ```\n",
    "\n",
    "3.  给用户 $u$ 推荐其自己经常使用的标签 （ **UserPopularTags** ）\n",
    "\n",
    "    令<code>user_tags[u][b]</code>为用户 $u$ 使用标签 $b$ 的次数，则算法实现如下：\n",
    "\n",
    "    ```python\n",
    "    def recommend_user_popular_tags(user, item, user_tags, N):\n",
    "        return sorted(user_tags[user].items(), key=itemgetter(1), reverse=True)[:N]\n",
    "    ```\n",
    "\n",
    "4.  融合第2种和第3种算法（ **HybridPopularTags** ）\n",
    "\n",
    "    通过一个系数将推荐结果线性加权，然后生成最终的推荐结果。算法实现如下：\n",
    "\n",
    "    ```python\n",
    "    def recommend_hybrid_popular_tags(user, item, user_tags, item_tags, alpha, N):\n",
    "        max_user_tag_weight = max(user_tags[user].values())\n",
    "        for tag, weight in user_tags[user].items():\n",
    "            ret[tag] = (1 – alpha) * weight / max_user_tag_weight\n",
    "\n",
    "        max_item_tag_weight = max(item_tags[item].values())\n",
    "        for tag, weight in item_tags[item].items():\n",
    "            if tag not in ret:\n",
    "                ret[tag] = alpha * weight / max_item_tag_weight\n",
    "            else:\n",
    "                ret[tag] += alpha * weight / max_item_tag_weight\n",
    "        return sorted(ret[user].items(), key=itemgetter(1), reverse=True)[:N]\n",
    "    ```\n",
    "\n",
    "    上面的实现中，在将两个列表线性相加时都将两个列表按最大值做了归一化，便于控制两个列表对最终结果的影响，而不至于因为物品非常热门而淹没用户对推荐结果的影响，或者因为用户非常活跃而淹没物品对推荐结果的影响。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"org68f02fb\"></a>\n",
    "### 实验设置\n",
    "\n",
    "用同样的方法将数据集按照 **9∶1** 分成训练集和测试集，然后通过训练集学习用户标注的模型。注意，这里切分数据集不再是以user、item为主键，而是以user、item、tag为主键。\n",
    "\n",
    "```python\n",
    "def split_data(records, train, test):\n",
    "    for user, item, tag in records:\n",
    "        if random.randint(1, 10) == 1:\n",
    "            test.append([user, item, tag])\n",
    "        else:\n",
    "            train.append([user, item, tag])\n",
    "    return [train, test]\n",
    "```\n",
    "\n",
    "对于测试集中的每一个用户物品对 $(u,i)$ ，推荐 $N$ 个标签给用户 $u$ 作参考。令 $R(u,i)$ 为给用户 $u$ 推荐的应该在物品 $i$ 上打的标签集合，令 $T(u,i)$ 为用户 $u$ 实际给物品 $i$ 打的标签的集合，利用准确率和召回率评测标签推荐的精度：\n",
    "\n",
    "\\begin{equation}\n",
    "\\begin{split}\n",
    "& Precision = \\frac{\\underset{(u,i) \\in Test}{\\sum} |R(u,i) \\cap T(u,i)|}{\\underset{(u,i) \\in Test}{\\sum} |R(u,i)|} \\\\\n",
    "& Recall = \\frac{\\underset{(u,i) \\in Test}{\\sum} |R(u,i) \\cap T(u,i)|}{\\underset{(u,i) \\in Test}{\\sum} |T(u,i)|} \\\\\n",
    "\\end{split} \\nonumber\n",
    "\\end{equation}\n",
    "\n",
    "很多应用在给用户推荐标签时会直接给出用户最常用的标签，以及物品最经常被打的标签。\n",
    "\n",
    "基于统计用户常用标签和物品常用标签的算法有一个缺点，就是对新用户或者不热门的物品很难有推荐结果。解决这一问题的两个思路：\n",
    "\n",
    "1.  从物品的内容数据中抽取关键词作为标签（参考上下文广告<sup><a id=\"fnr.6\" class=\"footref\" href=\"#fn.6\">6</a></sup>和关键词向量）\n",
    "2.  关键词扩展\n",
    "\n",
    "    针对有结果，但结果不太多的情况。实现标签扩展的关键就是计算标签之间的相似度。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "## 脚注\n",
    "\n",
    "<sup><a id=\"fn.1\" class=\"footnum\" href=\"#fnr.1\">1</a></sup> 参见<https://en.wikipedia.org/wiki/Tag_(metadata)>。\n",
    "\n",
    "<sup><a id=\"fn.2\" class=\"footnum\" href=\"#fnr.2\">2</a></sup> 参见Morgan Ames和Mor Naaman的“Why we tag: motivations for annotation in mobile and online media”（CHI 2007，2007）。\n",
    "\n",
    "<sup><a id=\"fn.3\" class=\"footnum\" href=\"#fnr.3\">3</a></sup> 参见<http://www.dai-labor.de/en/competence_centers/irml/datasets/>。\n",
    "\n",
    "<sup><a id=\"fn.4\" class=\"footnum\" href=\"#fnr.4\">4</a></sup> 还可能包括用户打标签的时间、用户的属性数据、物品的属性数据等。\n",
    "\n",
    "<sup><a id=\"fn.5\" class=\"footnum\" href=\"#fnr.5\">5</a></sup> 参见Jesse Vig、Shilad Wieland Sen和John Riedl的“Tagsplanations: Explaining Recommendations Using Tags”（ACM 2009 Article，2009）。\n",
    "\n",
    "<sup><a id=\"fn.6\" class=\"footnum\" href=\"#fnr.6\">6</a></sup> 参见Wen-tau Yih、Joshua Goodman和Vitor R. Carvalho的“Finding Advertising Keywords on Web Pages”（ACM 2006 Article，2006）。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "name": "9-user-tag-data.ipynb"
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
